/*
	Engine.cpp

	Contains the main SnowstormX engine functions.

	Author: Aidan Goddard 1/8/14
*/

#include"SnowstormX.h"
#include"SnowstormXEngineVars.h"

#include"Engine.h"
#include"EngineState.h"
#include"Error.h"
#include"Input.h"
#include"Worker.h"
#include"Dispatch.h"

#include<Windows.h>
#include<Window.h>

#include<iostream>

/////////////////////////////////////////////////////////////////////////////////////////////////
// some global stuff

XVARS *Snowstorm::Engine::ev = NULL;
Snowstorm::Window *Snowstorm::Engine::wnd;
SNOWSTORMX_PARAMS *Snowstorm::Engine::parameters;


/////////////////////////////////////////////////////////////////////////////////////////////////
// system information object (only used internally here)
class SYS_INFO
{
public:
	SYS_INFO();

public:
	uint32_t cpu_count;
};

SYS_INFO::SYS_INFO()
{
	// get number of CPUs
    SYSTEM_INFO s;
    GetSystemInfo(&s);
	this->cpu_count = s.dwNumberOfProcessors;



	// finished
	return;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// window events

void Snowstorm::Engine::FocusGainedEvent(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// check if cursor needs to be locked to the window
	if(parameters->cursor_lock_to_window)
	{
		POINT p;
		p.x = 0;
		p.y = 0;
		ClientToScreen(w->GetHWND(), &p);
		RECT rc;
		GetClientRect(w->GetHWND(), &rc);
		rc.left += p.x;
		rc.top += p.y;
		rc.right += p.x;
		rc.bottom += p.y;
		ClipCursor(&rc);
	}

	// finished
	return;
}

void Snowstorm::Engine::FocusLostEvent(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// unlock the mouse cursor
	ClipCursor(NULL);

	// check if should minimise it
	if(parameters->minimize_on_focus_lost)
	{
		// minimise it
		w->Minimize();
	}

	// finished
	return;
}

void Snowstorm::Engine::WindowSetCursor(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// check if cursor is in the window client area
	if(LOWORD(lparam) == HTCLIENT && !(parameters->cursor_visible))
	{
		SetCursor(NULL);
	}
	else
	{
		SetCursor(LoadCursor(NULL, IDC_ARROW));
	}

	// finished
	return;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// engine kill function

SNOWSTORMXDLL_API void XKill(uint64_t exit_code)
{
	Snowstorm::EngineState::engine_state.cmd_exit = true;
	Snowstorm::EngineState::engine_state.exit_return_code = exit_code;
	return;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// action pause/unpause functions

SNOWSTORMXDLL_API void XPauseAll()
{
	Snowstorm::EngineState::engine_state.cmd_pause_actions = PAUSE_STATE_ALL;
	return;
}

SNOWSTORMXDLL_API void XPauseMapEntities()
{
	Snowstorm::EngineState::engine_state.cmd_pause_actions = PAUSE_STATE_MAP;
	return;
}

SNOWSTORMXDLL_API void XResume()
{
	Snowstorm::EngineState::engine_state.cmd_unpause_actions = true;
	return;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// calls the main engine function from the outside (ooooohh)
SNOWSTORMXDLL_API uint64_t EngineRun(SNOWSTORMX_PARAMS *pa, Snowstorm::Entity *main_entity, void *main_params)
{
	// check inputs are sane
	if(!main_entity)
		Snowstorm::Error::SignalError(L"Main Entity NULL", L"ERROR: Main Entity cannot be NULL");

	if(!pa)
		Snowstorm::Error::SignalError(L"Engine Parameters NULL", L"ERROR: Engine Parameters cannot be NULL");

	return Snowstorm::Engine::EngineRun(pa, main_entity, main_params);
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// The main engine function.
// This is passed a pointer to the game's Main function (GameMain), which will initially be the
// only game code running in the system.
// When GameMain returns, the engine terminates and returns whatever GameMain returned.
// The engine may also be terminated (causing this function to return) when the game code function
// XKill is called.
// GameMain may optionally be passed a single void pointer which may provide user-defined arguments.


uint64_t Snowstorm::Engine::EngineRun(SNOWSTORMX_PARAMS *pa, Snowstorm::Entity *main_entity, void *main_params)
{
	// initialise the engine vars structure
	parameters = pa;
	ev = new XVARS;
	ev->current_FPS = 0;
	ev->frame_time = 1.0 / parameters->fps_max;
	ev->KEYSTATE[0] = 0;
	ev->KEYSTATE[1] = 0;
	ev->KEYSTATE[2] = 0;
	ev->KEYSTATE[3] = 0;
	ev->mickey_x = 0;
	ev->mickey_y = 0;
	ev->mickey_z = 0;
	ev->screen_x = 0;
	ev->screen_y = 0;
	ev->xbox_ltr = 0;
	ev->xbox_lx = 0;
	ev->xbox_ly = 0;
	ev->xbox_rtr = 0;
	ev->xbox_rx = 0;
	ev->xbox_ry = 0;
	ev->actions_paused = PAUSE_STATE_NONE;


	// get the system information
	SYS_INFO sys_info;


	// get number of threads to use
	uint32_t num_threads = sys_info.cpu_count;
	if(parameters->thread_usage > 1 && parameters->thread_usage <= sys_info.cpu_count)
	{
		num_threads = parameters->thread_usage;
	}
	else if(parameters->thread_usage == 0)
	{
		num_threads = sys_info.cpu_count;
	}
	else
	{
		num_threads = 2;
	}



	// now have the number of threads to use
	// initialise the main entity
	main_entity->SetThreadID(0);
	main_entity->InitExecutionData(main_params);
	main_entity->SetName(L"Main Entity", L"Game Main", 0, 0);


	// initialise the dispatcher
	Dispatch::InitDispatch(num_threads, main_entity);


	// create the worker objects
	Worker::CreateWorkers(num_threads - 1);


	// create the engine window system
	Window::Initialise(Snowstorm::MODE_SINGLE);
	WindowAttributes wa;
	wa.catch_input = true;
	wa.parent_hwnd = NULL;
	wa.x = 100;
	wa.y = 100;
	wa.title = parameters->window_title;
	wa.width = parameters->size_x;
	wa.height = parameters->size_y;
	
	if(parameters->screen_type == SCREEN_TYPE::WINDOWED)
		wa.window_type = Snowstorm::TYPE_POPUP;
	else if(parameters->screen_type == SCREEN_TYPE::FULLSCREEN)
		wa.window_type = Snowstorm::TYPE_FULLSCREEN;
	else if(parameters->screen_type == SCREEN_TYPE::EXCLUSIVE_FULLSCREEN)
		wa.window_type = Snowstorm::TYPE_NO_BOARDER;

	wnd = Window::Create(&wa);
	if(!wnd)
		Error::SignalError(L"No Window", L"ERROR: Could not create engine window");

	Error::main_window_hwnd = wnd->GetHWND();

	// show the window and lock the cursor to it
	wnd->Show();
	Engine::FocusGainedEvent(wnd, 0, 0);



	// register keyboard & mouse events
	wnd->RegisterKeyPressedEventHandler(Input::KeyPressEvent);
	wnd->RegisterKeyReleasedEventHandler(Input::KeyReleaseEvent);
	wnd->RegisterMouseEventHandler(Input::MouseEvent);
	wnd->RegisterHandler(WM_MOUSEMOVE, Input::MouseMoveEvent);

	// register other window events
	wnd->RegisterHandler(WM_CLOSE, Input::WindowCloseClicked);
	wnd->RegisterHandler(WM_KILLFOCUS, Engine::FocusLostEvent);
	wnd->RegisterHandler(WM_SETFOCUS, Engine::FocusGainedEvent);
	wnd->RegisterHandler(WM_SETCURSOR, Engine::WindowSetCursor);



	// get timer information
	LARGE_INTEGER t;
	QueryPerformanceFrequency(&t);
	double windows_timer_frequecy = (double)t.QuadPart;
	double seconds_per_frame = 1.0 / ((double)parameters->fps_max);
	double ticks_per_frame = (windows_timer_frequecy * seconds_per_frame);



	// only allow vsync if running in exclusive fullscreen
	bool vsync = parameters->vsync;
	if(parameters->screen_type == SCREEN_TYPE::WINDOWED || parameters->screen_type == SCREEN_TYPE::FULLSCREEN)
		vsync = false;


	// some in-loop variables
	double FPS_counter = 0; 
	double time_counter = 0;



	// frame loop
	while(1)
	{
		// start frame timer
		QueryPerformanceCounter(&t);
		double start_time = (double)t.QuadPart;
		double target_time = start_time + ticks_per_frame;


		// do window messages
		if(!Window::MessageLoop())
		{
			break;
		}

		
		// perform input events
		// (call dispatcher)
		Dispatch::SignalInputEventDispatch();


		// perform main function frame & game code
		// (call dispatcher)
		Dispatch::SignalGameCodeDispatch();



		// do timing things
		QueryPerformanceCounter(&t);

		if(vsync)
		{
			// wait on DirectX vsync

		}
		else if(t.QuadPart < target_time)
		{
			// wait on timer
			while(1)
			{
				QueryPerformanceCounter(&t);
				if(target_time <= t.QuadPart)
				{
					// manual switch frame buffer

					break;
				}
			}
		}

		// get the frame time in seconds
		QueryPerformanceCounter(&t);
		ev->frame_time = (double)((t.QuadPart - start_time) / windows_timer_frequecy);

		// get the FPS
		if(time_counter > 1)
		{
			ev->current_FPS = FPS_counter;
			FPS_counter = 0;
			time_counter = 0;
		}
		else
		{
			time_counter += ev->frame_time;
			FPS_counter++;
		}
		
		// end of frame
		// check post-frame engine commands
		// exit command
		if(EngineState::engine_state.cmd_exit)
		{
			// need to exit the engine
			break;
		}

		// pause actions command
		if(EngineState::engine_state.cmd_pause_actions > 0)
		{
			ev->actions_paused = EngineState::engine_state.cmd_pause_actions;
			EngineState::engine_state.cmd_pause_actions = 0;
		}

		// unpause actions command
		if(EngineState::engine_state.cmd_unpause_actions)
		{
			ev->actions_paused = false;
			EngineState::engine_state.cmd_unpause_actions = false;
		}

		// persistent entity changes
		if(EngineState::engine_state.cmd_create_persistent_entity || EngineState::engine_state.cmd_delete_persistent_entity)
		{
			// reset the commands
			EngineState::engine_state.cmd_create_persistent_entity = false;
			EngineState::engine_state.cmd_delete_persistent_entity = false;

			// update the persistent entity queues
			Dispatch::UpdatePersistentEntities();
		}


		// debugging only stuff
		#ifdef _DEBUG
		// debug buffer to print
		if(EngineState::engine_state.debug_buffer_length > 0)
		{
			// print the debug buffer and reset it
			std::wcout << EngineState::engine_state.debug_buffer << std::flush;
			EngineState::engine_state.debug_buffer_length = 0;
		}
		#endif
	}

	// peform cleanup and exit
	Window::Finish();

	return EngineState::engine_state.exit_return_code;
}